/**
 * @author Shen Yi - bm2736892@gmail.com
 */

var DeferredShading = {
	//========================public properties
	LightTypes : {
		"SPOT" : 1,
		"POINT" : 2,
		"DIRECT" : 3
	},
	
	size : 2,
	
	//========================private properties
	_cameras : [],
	_lights : [],
	_renderTargets : [],
	_materials : [],
	_finalRenderTarget : null,
	
	_SSAO : null,
	
	
	getRenderTargets : function(name){
		if(isset(name)){
			return this._renderTargets[name]
		}
		return this._renderTargets;
	},
	
	getFinalRenderTarget : function(){
		return this._finalRenderTarget;
	},
	
	createGBuffer : function(name, cameraName){
		this.GBuffer = new GBuffer(name, cameraName);
		this.prepare();
	},
	
	getGBuffer : function(){
		return this.GBuffer;
	},
	
	getSSAO : function(){
		return this._SSAO;
	},
	
	prepare : function(){
		var renderTarget = TextureManager.createRenderTarget2D("DS_rendertarget_prepare", 512*this.size, 512*this.size, "XRGB8");
		this._renderTargets["DS_prepare"] = renderTarget;
		
		var camera = SceneManager.createCamera("DS_camera_prepare", this._cameras.length+20, renderTarget.name);
		
		this._cameras["DS_prepare"] = camera;
		camera.setOrthographic();
		
		var material = MaterialManager.createMaterialFromShader("DS_material_prepare", "media/shaders/DeferredShading/prepare.shader");
		
		material.getParam("colorTex").value = this.GBuffer.getColorSampler();
		
		MaterialManager.bindCamera(material.name, camera.name);
		
		var plane = SceneManager.createPlane("output_plane_prepare", material.name, 2, 2, 2, 2);
		plane.rotateX(Math.PI / 2);
		SceneManager.addTransformTo(plane.name, camera.name);
		//update final render target;
		this._finalRenderTarget = renderTarget;
	},
	
	/**
	 * used name: rendertarget : DS_rendertarget_{lightName},
	 * 			sampler : DS_sampler_{lightName},
	 * 						DS_shadow_{lightName},
	 * 			cameara : DS_camera_{lightName}
	 * 			shape : DS_output_plane_{lightName}
	 * 			material : DS_material_{lightName}
	 * @param {Object} light
	 * @param {Boolean} renderToScene
	 */
	addLight : function(light, renderToScene){
		this._lights[light.name] = light;
		
		var renderTarget = TextureManager.createRenderTarget2D("DS_rendertarget_"+light.name, 512*this.size, 512*this.size, "XRGB8");
		this._renderTargets[renderTarget.name] = renderTarget;
		
		renderToScene = isset(renderToScene) ? renderToScene : false;
		if(renderToScene){
			//camera priority range [20, 40]
			var camera = SceneManager.createCamera("DS_camera_"+light.name, 20);
		}else{
			var camera = SceneManager.createCamera("DS_camera_"+light.name, 20, renderTarget.name);
		}
		this._cameras[camera.name] = camera;
		camera.setOrthographic();
		
		var material = MaterialManager.createMaterialFromShader("DS_material_"+light.name, "media/shaders/DeferredShading/phong.shader");
		this._materials[material.name] = material;
		
		this.updateGBuffer();
		
		var option = {"minFilter" : "ANISOTROPIC", "maxAnisotropy" : 4};
		var sampler = TextureManager.createSampler("DS_sampler_"+light.name, option);
		sampler.texture = this._finalRenderTarget;
		material.getParam("lightBuffer").value = sampler;
		
		material.getParam("colorTex").value = this.GBuffer.getColorSampler();
		material.getParam("normalTex").value = this.GBuffer.getNormalSampler();
		material.getParam("positionTex").value = this.GBuffer.getPositionSampler();
		material.getParam("parameterTex").value = this.GBuffer.getParameterSampler();
		material.getParam("lightColor").value = light.getColor();
		if(isset(light.getPosition())){
			material.getParam("lightPos").value = light.getPosition();
		}
		if(isset(light.getAttenuation())){
			material.getParam("attFactor").value = light.getAttenuation();
		}
		if(isset(light.getPower())){
			material.getParam("power").value = light.getPower();
		}
		
		material.getParam("hasShadowMap").value = 0;
		
		if(light.shadow){
			var sampler = TextureManager.createSampler("DS_shadow_" + light.name, {
				"minFilter" : "POINT",
				"magFilter" : "POINT",
				"mipFilter" : "POINT",
				"addressModeU" : "BORDER",
				"addressModeV" : "BORDER"
			})
			sampler.texture = light.shadow;
			material.getParam("shadow").value = sampler;
			material.getParam("hasShadowMap").value = 1;
		}
		if(this._SSAO){
			material.getParam("hasSSAO").value = 1;
			material.getParam("ssao").value = this._SSAO;
		}
		
		material.getParam("lightType").value = eval("(" + "this.LightTypes." + light.getType() + ")");
		if(light.getType() == "DIRECT"){
			material.getParam("lightDirect").value = light.getDirection();
			material.getParam("attFactor").value = 0;
		}
		
		MaterialManager.bindCamera(material.name, camera.name);
		
		var plane = SceneManager.createPlane("DS_output_plane_" + light.name, material.name, 2, 2, 2, 2);
		plane.rotateX(Math.PI / 2);
		SceneManager.addTransformTo(plane.name, camera.name);
		//update final render target;
		this._finalRenderTarget = renderTarget;
	},
	
	/**
	 * a function to judge if a pixel is in the shadow
	 * @param {Object} light the light should has a shadow map created before
	 */
	calShadowMap : function(light)
	{
		var renderTarget = TextureManager.createRenderTarget2D("DSSM_rendertarget_"+light.name, 512, 512, "XRGB8");
		this._renderTargets[renderTarget.name] = renderTarget;
		
		var camera = SceneManager.createCamera("DSSM_camera_"+light.name, 21, renderTarget.name);
		this._cameras[camera.name] = camera;
		camera.setOrthographic();
		
		var material = MaterialManager.createMaterialFromShader("DSSM_material_"+light.name, "media/shaders/DeferredShading/calShadow.shader");
		this._materials[material.name] = material;
		
		this.updateGBuffer();
		material.getParam("lightViewProjection").value = light.getLightViewProjection();
		material.getParam("lightPos").value = light.getCamera().getCameraInfo().eye;
		
		material.getParam("positionTex").value = this.GBuffer.getPositionSampler();
		if(!light.getShadowMapSampler()){
			error("call the light.createShadowMap() first!!");
		}
		material.getParam("shadowMap").value = light.getShadowMapSampler();
		
		MaterialManager.bindCamera(material.name, camera.name);
		
		var plane = SceneManager.createPlane("DSSM_output_plane_" + light.name, material.name, 2, 2, 2, 2);
		plane.rotateX(Math.PI / 2);
		SceneManager.addTransformTo(plane.name, camera.name);
		
		//add the sampler to the light property
		light.shadow = renderTarget;
		
		return renderTarget;
	},
	/**
	 * used name: rendertarget : SSAO_rendertarget,
	 * 			sampler : SSAO_sampler,
	 * 			cameara : SSAO_camera
	 * 			shape : SSAO_plane
	 * 			material : SSAO_material_1
	 * 						SSAO_material_2
	 * 							...
	 * 						SSAO_material_8
	 * 
	 * because the limit of ps_2_0, we have to seperate the calculate of the ssao into 8 pass, 2 shader;
	 * 
	 * create a screen based ambient occlusion map
	 */
	createSSAO : function(){
		var samples = [[],[],[],[]];
		var renderTarget = TextureManager.createRenderTarget2D("SSAO_rendertarget", 512*2, 512*2, "XRGB8");
		
		var camera = SceneManager.createCamera("SSAO_camera", 41, renderTarget.name);
		this._cameras[camera.name] = camera;
		camera.setOrthographic();
		
		var vec = [[0, 1], [1, 0], [-1, 0], [0, -1]];
		
		for (var i = 1; i <= 4; i++) {
			var material = MaterialManager.createMaterialFromShader("SSAO_material_" + i, "media/shaders/DeferredShading/SSAO" + i + ".shader");
			material.getParam("positionTex").value = this.GBuffer.getPositionSampler();
			material.getParam("normalTex").value = this.GBuffer.getNormalSampler();
			material.getParam("sample_rad").value = 0.1;
			material.getParam("intensity").value = 1000;
			material.getParam("scale").value = 10;
			material.getParam("bias").value = 0;
			material.getParam("vec").value = vec[0];
			this._materials["SSAO_material_"+i] = material;
		}
		
		for(var i = 5; i <= 16; i++){
			from = 1;
			if(i % 4 == 0){
				var from = 4;
			}
			this._materials["SSAO_material_"+i] = MaterialManager.copyMaterial("SSAO_material_" + i, "SSAO_material_" + from);
		}
		
		TextureManager.createSampler("media/texture/random_normal.jpg", {
			"minFilter" : "ANISOTROPIC", 
			"maxAnisotropy" : 4,
			"src" : "media/texture/random_normal.jpg"
		}, function(sampler){
			for (var i = 1; i <= 16; i++) {
				DeferredShading._materials["SSAO_material_"+i].getParam("randomTex").value = sampler;
			}
		})
		
		
		this._materials[material.name] = material;
		this.updateGBuffer();
		
		MaterialManager.bindCamera("SSAO_material_1", camera.name);

		var plane = SceneManager.createPlane("SSAO_plane", "SSAO_material_1", 2, 2, 2, 2);
		
		for(var i = 2; i <= 16; i++){
			//create a new pass
			this._materials["SSAO_material_"+i].getParam("vec").value = vec[parseInt((i-1)/4)];
			camera.createPass("pass_"+i, {});
			//create a new drawlist in the transform for material2
			SceneManager.bindTransformToCamera("SSAO_plane", "SSAO_material_"+i, camera.name, "pass_"+i);
		}

		plane.rotateX(Math.PI / 2);
		SceneManager.addTransformTo(plane.name, camera.name);
		
		//blur the ssao map
		var renderTarget1 = PostProcessing.gauss(renderTarget, 1);
		
		this._renderTargets[renderTarget.name] = renderTarget1;
		
		var sampler = TextureManager.createSampler("SSAO_sampler", {
			"minFilter" : "ANISOTROPIC", 
			"maxAnisotropy" : 4,
		});
		sampler.texture = this._renderTargets[renderTarget.name];
		this._SSAO = sampler;
		
		return renderTarget;
	},
	/**
	 * 
	 * @param {Object} param
	 * @param {Object} value
	 */
	updateSSAO : function(param, value){
		for (var i = 1; i <= 16; i++) {
			MaterialManager.getMaterial("SSAO_material_"+i).getParam(param).value = parseFloat(value);
		}
	},
	
	updateGBuffer : function(){
		this.GBuffer.updateCamera();
		
		for(var materialName in this._materials){
			var mmatrix4 = o3djs.math.matrix4;
			var mainCamera = SceneManager.getMainCamera();
			this._materials[materialName].getParam("viewProjInverse").value = mmatrix4.inverse(mmatrix4.mul(mainCamera.getCameraMatrix(), mainCamera.getProjectionMatrix()));
			if(this._materials[materialName].getParam("view")){
				this._materials[materialName].getParam("view").value = mainCamera.getCameraInfo().eye;
			}
		}
	}
}

var GBuffer = function(name, cameraName){
	/*==================public property====================*/
	this.root = cameraName;
	
	/*==================private property====================*/
	/**
	 * texture2d store the color info
	 */
	this._color = TextureManager.createRenderTarget2D("color_buffer_"+name, 512*DeferredShading.size, 512*DeferredShading.size, "XRGB8");
	/**
	 * texture2d store the normal info
	 */
	this._normal = TextureManager.createRenderTarget2D("normal_buffer_"+name, 512*DeferredShading.size, 512*DeferredShading.size, "XRGB8");
	/**
	 * texture2d store some parameters, such as shininess
	 */
	this._parameter = TextureManager.createRenderTarget2D("parameter_buffer_"+name, 1024, 1024, "ABGR32F");
	/**
	 * texture2d store the position info
	 */
	this._position = TextureManager.createRenderTarget2D("position_buffer_"+name, 1024, 1024, "ABGR32F");
	
	var option = {"minFilter" : "ANISOTROPIC", "maxAnisotropy" : 4};
	this._colorSampler = TextureManager.createSampler("color_sampler_"+name, option);
	this._normalSampler = TextureManager.createSampler("normal_sampler_"+name, option);
	this._parameterSampler = TextureManager.createSampler("parameter_sampler_"+name, option);
	this._positionSampler = TextureManager.createSampler("position_sampler_"+name, option);
	this._colorSampler.texture = this._color;
	this._normalSampler.texture = this._normal;
	this._parameterSampler.texture = this._parameter;
	this._positionSampler.texture = this._position;
	
	this._colorCamera = SceneManager.createCamera("color_camera_"+name, 10, "color_buffer_"+name);
	this._normalCamera = SceneManager.createCamera("normal_camera_"+name, 11, "normal_buffer_"+name);
	this._positionCamera = SceneManager.createCamera("position_camera_"+name, 12, "position_buffer_"+name);
	this._parameterCamera = SceneManager.createCamera("parameter_camera_"+name, 13, "parameter_buffer_"+name);
	
	this.updateCamera();
	
	var transform = SceneManager.getTransform(cameraName);
	//TODO : the parent should be the cameraTransform's parent, but it is undefined
	var parent = SceneManager._client.root;
	//insert the color transform, normal transform, position transform between the transform and 
	//transform's parent
	transform.parent = SceneManager.getTransform("color_camera_"+name);
	SceneManager.addTransformTo("color_camera_"+name, "normal_camera_"+name);
	SceneManager.addTransformTo("normal_camera_"+name, "position_camera_"+name);
	SceneManager.addTransformTo("position_camera_"+name, "parameter_camera_"+name);
	SceneManager.getTransform("parameter_camera_"+name).parent = parent;
	
	
	positionMaterial = MaterialManager.createMaterialFromShader("position_material_"+name, "media/shaders/DeferredShading/position.shader");
	var colorMaterialTemplate = MaterialManager.createMaterialFromShader("color_material_template", "media/shaders/DeferredShading/color.shader");
	var normalMaterialTemplate = MaterialManager.createMaterialFromShader("normal_material_template", "media/shaders/DeferredShading/normal.shader");
	var parameterMaterialTemplate = MaterialManager.createMaterialFromShader("parameter_material_template", "media/shaders/DeferredShading/parameter.shader");
	
	SceneManager.bindTransformToCamera(cameraName, "position_material_"+name, "position_camera_"+name);
	//TODO: the primitive should only be owned by cameraName
	var primitives = SceneManager.getPrimitive();
	for(var i = 0; i < primitives.length; i++){
		var material = primitives[i].material;
		if(material){
			if(isset(MaterialManager.getOption(material.name))){
				var colorMaterial = MaterialManager.copyMaterial("color_material_"+material.name, "color_material_template");
				var parameterMaterial = MaterialManager.copyMaterial("parameter_material_"+material.name, "parameter_material_template");
				var normalMaterial = MaterialManager.copyMaterial("normal_material_"+material.name, "normal_material_template");
				//TODO : get the value from the option
				var option = MaterialManager.getOption(material.name);
				if(isset(option.Variable.shininess)){
					parameterMaterial.getParam("shininess").value = option.Variable.shininess.defaultValue;
				}else{
					parameterMaterial.getParam("shininess").value = 0;
					parameterMaterial.getParam("skybox").value = 0;
				}
				
				if(material.getParam("normalSampler")){
					normalMaterial.getParam("normalSampler").value = material.getParam("normalSampler").value;
					normalMaterial.getParam("hasNormalSampler").value = material.getParam("hasNormalSampler").value;
				}
				
				colorMaterial.getParam("diffuse").value = material.getParam("diffuse").value;
				colorMaterial.getParam("diffuseSampler").value = material.getParam("diffuseSampler").value;
				colorMaterial.getParam("hasTex").value = material.getParam("hasTex").value;
				
				SceneManager.bindPrimitiveToCamera(primitives[i].name, "color_material_"+material.name, "color_camera_"+name);
				SceneManager.bindPrimitiveToCamera(primitives[i].name, "parameter_material_"+material.name, "parameter_camera_"+name);
				SceneManager.bindPrimitiveToCamera(primitives[i].name, "normal_material_"+material.name, "normal_camera_"+name);
			}
			//default material built in system
			else if(material.name == "SKY_BOX"){
				var parameterMaterial = MaterialManager.copyMaterial("parameter_material_"+material.name, "parameter_material_template");
				parameterMaterial.getParam("skybox").value = 1;
				SceneManager.bindPrimitiveToCamera(primitives[i].name, "parameter_material_"+material.name, "parameter_camera_"+name);
				colorMaterial = MaterialManager.copyMaterial("color_material_"+material.name, "SKY_BOX");
				MaterialManager.bindCamera("SKY_BOX", "color_camera_"+name);
			}
		}
	}
}

GBuffer.prototype = {
	getColorSampler : function(){
		return this._colorSampler;
	},
	getNormalSampler : function(){
		return this._normalSampler;
	},
	getPositionSampler : function(){
		return this._positionSampler;
	},
	getParameterSampler : function(){
		return this._parameterSampler;
	},
	updateCamera : function(){
		var mainCamera = SceneManager.getMainCamera();
		var mainCameraInfo = mainCamera.getCameraInfo();
		var mainProjectionMatrix = mainCamera.getProjectionMatrix();
	
		this._colorCamera.setProjectionMatrix(mainProjectionMatrix);
		this._colorCamera.setCamera(mainCameraInfo.eye, mainCameraInfo.target, mainCameraInfo.up);
		
		this._normalCamera.setProjectionMatrix(mainProjectionMatrix);
		this._normalCamera.setCamera(mainCameraInfo.eye, mainCameraInfo.target, mainCameraInfo.up);
		
		this._positionCamera.setProjectionMatrix(mainProjectionMatrix);
		this._positionCamera.setCamera(mainCameraInfo.eye, mainCameraInfo.target, mainCameraInfo.up);
		
		this._parameterCamera.setProjectionMatrix(mainProjectionMatrix);
		this._parameterCamera.setCamera(mainCameraInfo.eye, mainCameraInfo.target, mainCameraInfo.up);
	}
}
